﻿using System;
using System.Linq;
using TestBat.IModels.Domain;
using TestBat.IModels.Repositories;
using TestBat.IModels.Services;
using TestBat.Models.Attributes;
using TestBat.Models.Exceptions;
using TestBat.Models.Extensions;
using TestBat.ServiceLocator;

namespace TestBat.Models.Services
{
    public class ResultsService : IResultsService
    {
        public ResultsService(ITraineeRepository traineeRepository, IScheduleRepository scheduleRepository, ITestRepository testRepository)
        {
            this.traineeRepository = traineeRepository;
            this.testRepository = testRepository;
            this.scheduleRepository = scheduleRepository;
        }

        [Conversation]
        public IScheduleResults GetResults(ISchedule schedule, bool refresh)
        {
            if (refresh)
            {
                scheduleRepository.Refresh(schedule);
                //foreach (ITest test in schedule.Tests)
                //    testRepository.Refresh(test);
            }

            IScheduleResults results = DefaultServiceLocator.Get<IScheduleResults>(); // TODO: use DI

            results.NumTrainees = (from n in schedule.TraineeGroups from l in n.TraineeGroup.Trainees select l).Count<ITrainee>();
            results.NumTraineesTookTestGraded = (from n in schedule.TraineeGroups from l in n.TraineeGroup.Trainees where (from t in schedule.Tests where t.IsClosed && t.IsGraded && t.Trainee == l select t).Count<ITest>() != 0 select l).Count<ITrainee>();
            results.NumTraineesTookTestNotGraded = (from n in schedule.TraineeGroups from l in n.TraineeGroup.Trainees where (from t in schedule.Tests where t.IsClosed && !t.IsGraded && t.Trainee == l select t).Count<ITest>() != 0 select l).Count<ITrainee>();
            results.NumTraineesNotTookTest = (from n in schedule.TraineeGroups from l in n.TraineeGroup.Trainees where (from t in schedule.Tests where t.Trainee == l select t).Count<ITest>() == 0 select l).Count<ITrainee>();
            results.NumTraineesTakeTestNow = (from n in schedule.TraineeGroups from l in n.TraineeGroup.Trainees where (from t in schedule.Tests where !t.IsClosed && t.Trainee == l select t).Count<ITest>() != 0 select l).Count<ITrainee>();

            if ((from t in schedule.Tests where t.IsClosed && t.IsGraded select (from tt in t.TestTasks select (int)tt.MaximumPoints).Sum()).Sum() > 0)
            {
                results.AverageCorrect = (from t in schedule.Tests
                                          where t.IsClosed && t.IsGraded
                                          select (double)(from tt in t.TestTasks select tt.Solution.GetPercentage().Value * tt.MaximumPoints).Sum() / (from tt in t.TestTasks select (int)tt.MaximumPoints).Sum()).Average(x => x);
                results.MedianCorrect = (from t in schedule.Tests
                                         where t.IsClosed && t.IsGraded
                                         select (double)(from tt in t.TestTasks select tt.Solution.GetPercentage().Value * tt.MaximumPoints).Sum() / (from tt in t.TestTasks select (int)tt.MaximumPoints).Sum()).Median();
            }
            else
            {
                results.AverageCorrect = null;
                results.MedianCorrect = null;
            }

            if (results.AverageCorrect.HasValue)
                results.DefaultDeviationCorrect = Math.Sqrt((from p in (from t in schedule.Tests where t.IsClosed && t.IsGraded select ((double)(from tt in t.TestTasks select tt.Solution.GetPercentage().Value * tt.MaximumPoints).Sum() / (from tt in t.TestTasks select (int)tt.MaximumPoints).Sum())) select Math.Pow(p - results.AverageCorrect.Value, 2)).Sum());
            else
                results.DefaultDeviationCorrect = null;

            return results;
        }

        [Conversation]
        // TODO: needs rework
        public ITestResults GetResults(ITest test)
        {

            if (test == null)
                throw new BusinessException("Error getting results", new InvalidOperationException("Trainee didn't take test"));

            if (!test.IsClosed)
                throw new BusinessException("Error getting results", new InvalidOperationException("Test isn't closed"));

            if (!test.IsGraded)
                throw new BusinessException("Error getting results", new InvalidOperationException("Test isn't graded"));

            ITestResults results = DefaultServiceLocator.Get<ITestResults>(); // TODO: use DI

            if (test.TestTasks.Count == 0)
                results.PercentageCorrect = null;
            else
                results.PercentageCorrect = (from tt in test.TestTasks select ((float)tt.Solution.GetPercentage().Value / 100.0f) * (float)tt.MaximumPoints).Sum() / (from tt in test.TestTasks select (float)tt.MaximumPoints).Sum() * 100.0f;

            results.MaximumPoints = (uint)(from tt in test.TestTasks select (int)tt.MaximumPoints).Sum();

            if (test.TestTasks.Count == 0)
                results.Rank = null;
            else
                results.Rank = (from percentageCorrect in (from t in test.Schedule.Tests where t.IsGraded select (from tt in t.TestTasks select tt.Solution.GetPercentage().Value * tt.MaximumPoints).Sum() / (from tt in t.TestTasks select (int)tt.MaximumPoints).Sum()) where percentageCorrect > results.PercentageCorrect.Value select percentageCorrect).Count<long>() + 1;
            results.NumGradedTests = (from t in test.Schedule.Tests where t.IsGraded select t).Count();

            results.PerformedTasks = (from tt in test.TestTasks where !tt.Solution.IsEmtpy select tt).Count<ITestTask>();
            results.NotPerformedTasks = (from tt in test.TestTasks where tt.Solution.IsEmtpy select tt).Count<ITestTask>();
            results.PresentedTasks = (from tt in test.TestTasks select tt).Count<ITestTask>();

            return results;
        }

        private readonly ITraineeRepository traineeRepository;
        private readonly ITestRepository testRepository;
        private readonly IScheduleRepository scheduleRepository;
    }
}
